/* EIX - IPC system
 * Copyright (C) 2008-2011 Jorge Luis Zapata
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library.
 * If not, see <http://www.gnu.org/licenses/>.
 */
#include "Eix.h"
#include "eix_private.h"

#include <unistd.h>
#include <fcntl.h>

#ifdef HAVE_SYS_SOCKET_H
# include <sys/socket.h>
#endif

#ifdef HAVE_SYS_UN_H
# include <sys/un.h>
#endif
/*============================================================================*
 *                                  Local                                     *
 *============================================================================*/
typedef struct _Eix_Transport_Unix_Tcp
{
	Eix_Transport *t;
	int fd;
	struct sockaddr_un un;
} Eix_Transport_Unix_Tcp;

static Eix_Transport_State _eix_transport_unix_tcp_server_process(void *data,
		Eix_Transport_State state,
		Eina_Bool can_read, Eina_Bool can_write);
static Eix_Transport_State _eix_transport_unix_tcp_client_process(void *data,
		Eix_Transport_State state,
		Eina_Bool can_read, Eina_Bool can_write);
/*----------------------------------------------------------------------------*
 *                          The transport interface                           *
 *----------------------------------------------------------------------------*/
static void _eix_transport_unix_tcp_free(void *data)
{
	Eix_Transport_Unix_Tcp *thiz = data;
	close(thiz->fd);
	free(thiz);
}

static Eix_Transport_State _eix_transport_unix_tcp_serve(void *data)
{
	Eix_Transport_Unix_Tcp *thiz = data;

	/* TODO check ret value */
	unlink(thiz->un.sun_path);
	/* bind it */
	if (bind(thiz->fd, (struct sockaddr *)&thiz->un, sizeof(thiz->un)) < 0)
	{
		goto error_bind;
	}
	/* listen */
	if (listen(thiz->fd, 1024) < 0)
	{
		goto error_listen;
	}
	eix_transport_process_request(thiz->t, EINA_TRUE, EINA_FALSE);
	eix_transport_process_set(thiz->t, _eix_transport_unix_tcp_server_process);
	return EIX_TRANSPORT_STATE_CONNECTED;
error_listen:
error_bind:
	return EIX_TRANSPORT_STATE_INIT;
}

static Eix_Transport_State _eix_transport_unix_tcp_connect(void *data)
{
	Eix_Transport_Unix_Tcp *thiz = data;
	Eix_Transport_State s = EIX_TRANSPORT_STATE_INIT;
	Eina_Bool to_write = EINA_FALSE;
	int ret;

	/* connect */
	ret = connect(thiz->fd, (struct sockaddr *)&thiz->un, sizeof(thiz->un));
	if (ret < 0)
	{
 		if (errno != EINPROGRESS) goto done;
		to_write = EINA_TRUE;
		s = EIX_TRANSPORT_STATE_CONNECTING;
	}
	else
	{
		s = EIX_TRANSPORT_STATE_CONNECTED;
	}
	/* call the descriptor server add */
	eix_transport_process_request(thiz->t, EINA_TRUE, to_write);
	eix_transport_process_set(thiz->t, _eix_transport_unix_tcp_client_process);
done:
	return s;
}

static Eix_Transport_Status _eix_transport_unix_tcp_send(void *data, void *buf,
		size_t *len)
{
	Eix_Transport_Unix_Tcp *thiz = data;
	ssize_t ret;

	ret = send(thiz->fd, buf, *len, 0);
	if (ret < 0)
	{
		if (errno == EAGAIN || errno == EWOULDBLOCK)
			return EIX_TRANSPORT_STATUS_ASYNC;
		else
		{
			ERR("Error sending %d '%s'", errno, strerror(errno));
			return EIX_TRANSPORT_STATUS_FAILED;
		}
	}
	else
	{
		*len = ret;
		return EIX_TRANSPORT_STATUS_OK;
	}
}

static Eix_Transport_Status _eix_transport_unix_tcp_recv(void *data, void *buf,
		size_t *len)
{
	Eix_Transport_Unix_Tcp *thiz = data;
	ssize_t ret;

	ret = recv(thiz->fd, buf, *len, 0);
	if (!ret) return EIX_TRANSPORT_STATUS_FAILED;
	if (ret < 0)
	{
		if (errno == EAGAIN || errno == EWOULDBLOCK)
			return EIX_TRANSPORT_STATUS_ASYNC;
		else
			return EIX_TRANSPORT_STATUS_FAILED;
	}
	else
	{
		*len = ret;
		return EIX_TRANSPORT_STATUS_OK;
	}
}

static Eina_Bool _eix_transport_unix_tcp_wait_read(void *data)
{
	Eix_Transport_Unix_Tcp *thiz = data;
	return eix_transport_posix_wait_read(thiz->fd);
}

static Eina_Bool _eix_transport_unix_tcp_wait_write(void *data)
{
	Eix_Transport_Unix_Tcp *thiz = data;
	return eix_transport_posix_wait_write(thiz->fd);
}

static Eix_Transport_Descriptor _eix_transport_unix_tcp_descriptor = {
	/* .free 		= */ _eix_transport_unix_tcp_free,
	/* .serve 		= */ _eix_transport_unix_tcp_serve,
	/* .connect 		= */ _eix_transport_unix_tcp_connect,
	/* .recv 		= */ _eix_transport_unix_tcp_recv,
	/* .send 		= */ _eix_transport_unix_tcp_send,
	/* .wait_read		= */ _eix_transport_unix_tcp_wait_read,
	/* .wait_write		= */ _eix_transport_unix_tcp_wait_write,
};
/*----------------------------------------------------------------------------*
 *                          The processing functions                          *
 *----------------------------------------------------------------------------*/
static Eix_Transport_State _eix_transport_unix_tcp_server_process(void *data,
		Eix_Transport_State state,
		Eina_Bool can_read, Eina_Bool can_write)
{
	Eix_Transport_Unix_Tcp *thiz = data;
	Eix_Transport_Unix_Tcp *c_thiz = NULL;
	Eix_Transport *c;
	socklen_t addrlen;
	int ret;

	if (!can_read) goto done;

	/* ok we got data, it means a new client */
	c_thiz = calloc(1, sizeof(Eix_Transport_Unix_Tcp));
	addrlen = sizeof(c_thiz->un);
	ret = accept(thiz->fd, (struct sockaddr *)&c_thiz->un, &addrlen);
	if (ret < 0) goto error;

	c = eix_transport_new(&_eix_transport_unix_tcp_descriptor, c_thiz);
	eix_transport_process_set(c, _eix_transport_unix_tcp_client_process);
	eix_transport_process_request(c, EINA_TRUE, EINA_FALSE);
	c_thiz->t = c;
	c_thiz->fd = ret;

	/* make it non blocking */
	if (fcntl(ret, F_SETFL, O_NONBLOCK) < 0) goto error;
	if (fcntl(ret, F_SETFD, FD_CLOEXEC) < 0) goto error;

	/* call the added callback */
	eix_transport_process_add(thiz->t, c);
	goto done;

error:
	if (c_thiz->fd)
		close(c_thiz->fd);
	if (c_thiz)
		free(c_thiz);
done:
	return EIX_TRANSPORT_STATE_CONNECTED;
}

static Eix_Transport_State _eix_transport_unix_tcp_client_process(void *data,
		Eix_Transport_State state,
		Eina_Bool can_read, Eina_Bool can_write)
{
	Eix_Transport_Unix_Tcp *thiz = data;

	if (can_write)
	{
		/* call the added callback */
		if (state == EIX_TRANSPORT_STATE_CONNECTING)
		{
			eix_transport_process_add(thiz->t, NULL);
		}
	}

	if (can_read)
	{
		eix_transport_recv(thiz->t);
	}
	return EIX_TRANSPORT_STATE_CONNECTED;
}
/*============================================================================*
 *                                 Global                                     *
 *============================================================================*/
/*============================================================================*
 *                                   API                                      *
 *============================================================================*/
EAPI Eix_Transport * eix_transport_unix_tcp_new(const char *name)
{
	Eix_Transport *t;
	Eix_Transport_Unix_Tcp *thiz;
	int fd;

	fd = socket(AF_UNIX, SOCK_STREAM, 0);
	if (fd < 0)
	{
		goto error_socket;
	}
	/* make it non blocking */
	if (fcntl(fd, F_SETFL, O_NONBLOCK) < 0) goto error_fcntl;
	if (fcntl(fd, F_SETFD, FD_CLOEXEC) < 0) goto error_fcntl;

	thiz = calloc(1, sizeof(Eix_Transport_Unix_Tcp));
	thiz->un.sun_family = AF_UNIX;
	strncpy(thiz->un.sun_path, name, sizeof(thiz->un.sun_path) - 1);

	t = eix_transport_new(&_eix_transport_unix_tcp_descriptor, thiz);
	thiz->t = t;
	thiz->fd = fd;

	return t;

error_fcntl:
	close(fd);
error_socket:
	return NULL;
}

EAPI const char * eix_transport_unix_tcp_path_get(Eix_Transport *t)
{
	Eix_Transport_Unix_Tcp *thiz;

	thiz = eix_transport_data_get(t);
	return thiz->un.sun_path;
}

EAPI int eix_transport_unix_tcp_fd_get(Eix_Transport *t)
{
	Eix_Transport_Unix_Tcp *thiz;

	thiz = eix_transport_data_get(t);
	return thiz->fd;
}
